﻿// -----------------------------------------------------------------------
// <copyright file="ContextHelper.cs" company="PlanB.">
// PlanB.
// </copyright>
// -----------------------------------------------------------------------

namespace PlanB.SharePointCore.Helpers
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Web;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.WebControls;

    /// <summary>
    /// A class which either stores or uses existing SharePoint context, if provided in the constructor in a form of SPWeb object
    /// or creates a new context, if a path to the site collection and web is provided in the consturctor as a string.
    /// </summary>
    public class ContextHelper
    {
        #region private members
        /// <summary>
        /// Web object
        /// </summary>
        private SPWeb sharepointWeb;

        /// <summary>
        /// Context Site object
        /// </summary>
        private SPSite sharepointSite;

        /// <summary>
        /// Web under the elevated permissions
        /// </summary>
        private SPWeb elevatedWeb;

        /// <summary>
        /// Site under the elevated permissions
        /// </summary>
        private SPSite elevatedSite;

        /// <summary>
        /// Context type - SPWeb passed or initialized?
        /// </summary>
        private ContextTypes contextType;

        /// <summary>
        /// Should the the context with the elevated privileges be created as well
        /// </summary>
        private bool useElevation;
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="ContextHelper"/> class  - check the type of the passed context, and, if necessary, instantiate SPWeb
        /// </summary>
        /// <param name="context">Context object which we have received</param>
        public ContextHelper(object context)
        {
            this.useElevation = false;
            this.InitializeContext(context, false);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ContextHelper"/> class - check the type of the passed context, and, if necessary, instantiate SPWeb
        /// </summary>
        /// <param name="context">Context object which we have received</param>
        /// <param name="elevatedPermissions">if set to <c>true</c>, create the context with elevated permissions.</param>
        public ContextHelper(object context, bool elevatedPermissions)
        {
            this.useElevation = elevatedPermissions;
            this.InitializeContext(context, elevatedPermissions);
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="ContextHelper"/> class.
        /// When disposing ContextHelper, look in the destructor if the SPWeb is disposed - if it should be disposed at all. 
        /// Dispose it if it isn't 
        /// </summary>
        ~ContextHelper()
        {
            this.DisposeContext();
        }

        #region Enums
        /// <summary>
        /// Which context do we have - one which is passed from the SPContext (Front end), or do we create the context ourselves
        /// </summary>
        public enum ContextTypes
        {
            /// <summary>
            /// The context object which has been passed to the ContextHelper class is unknwon
            /// </summary>
            Unknown = 0,

            /// <summary>
            /// The context is passed from the front end
            /// </summary>
            Spcontext = 1,

            /// <summary>
            /// We need to initialize the context ourselves
            /// </summary>
            Initialize = 2
        }
        #endregion

        #region Public properties
        /// <summary>
        /// Gets a SPWeb object from context
        /// </summary>
        public SPWeb Web
        {
            get
            {
                return this.sharepointWeb;
            }
        }

        /// <summary>
        /// Gets a SPSite from context
        /// </summary>
        public SPSite Site
        {
            get
            {
                return this.sharepointSite;
            }
        }

        /// <summary>
        /// Gets a Elevated SPWeb object from context, if elevation has been used
        /// </summary>
        public SPWeb ElevatedWeb
        {
            get
            {
                if (this.useElevation)
                {
                    return this.elevatedWeb;
                }
                else
                {
                    throw new ApplicationException("Elevation has not been used in the context helper");
                }
            }
        }

        /// <summary>
        /// Gets a Elevated SPSite from context
        /// </summary>
        public SPSite ElevatedSite
        {
            get
            {
                if (this.useElevation)
                {
                    return this.elevatedSite;
                }
                else
                {
                    throw new ApplicationException("Elevation has not been used in the context helper");
                }
            }
        }

        /// <summary>
        /// Gets a current Context type - is SPWeb passed or newly initialized?
        /// </summary>
        public ContextTypes ContextType
        {
            get
            {
                return this.contextType;
            }
        }
        #endregion

        #region public methods
        /// <summary>
        /// Dispose context
        /// </summary>
        public void DisposeContext()
        {
            // Dispose elevated web and site, if used
            if (this.useElevation)
            {
                try
                {
                    // Dispose elevated web and site
                    if (this.elevatedWeb != null)
                    {
                        this.elevatedWeb.Dispose();
                    }

                    if (this.elevatedSite != null)
                    {
                        this.elevatedSite.Dispose();
                    }
                }
                catch (Exception ex)
                {
                    // TODO AG: Exception handling here
                    throw new ApplicationException("Could not dispose the elevated context", ex);
                }
            }

            // Dispose the context if it was initialized manually (not passed from the Front End)
            if (this.contextType == ContextTypes.Initialize)
            {
                try
                {
                    // Dispose web and site
                    if (this.sharepointWeb != null)
                    {
                        this.sharepointWeb.Dispose();
                    }

                    if (this.sharepointSite != null)
                    {
                        this.sharepointSite.Dispose();
                    }
                }
                catch (Exception ex)
                {
                    // TODO AG: Exception handling here
                    throw new ApplicationException("Could not dispose the context", ex);
                }
            }
        }
        #endregion

        #region private methods
        /// <summary>
        /// Initializes the context - check the type of the passed context, and, if necessary, instantiate SPWeb
        /// </summary>
        /// <param name="context">Context object which we have received</param>
        /// <param name="elevatedPermissions">if set to <c>true</c>, create the context with elevated permissions.</param>
        private void InitializeContext(object context, bool elevatedPermissions)
        {
            this.contextType = ContextTypes.Unknown;

            if (context is SPWeb)
            {
                this.contextType = ContextTypes.Spcontext;
                this.sharepointWeb = context as SPWeb;
                this.sharepointSite = this.sharepointWeb.Site;
            }
            else if (context is string)
            {
                this.contextType = ContextTypes.Initialize;
                this.CreateContextFromString(context as string);
            }

            if (this.useElevation)
            {
                // WebApplication.Farm.DefaultServiceAccount 
                SPUserToken sysToken = this.sharepointSite.SystemAccount.UserToken;

                this.elevatedSite = new SPSite(this.sharepointSite.ID, sysToken);
                this.elevatedWeb = this.elevatedSite.OpenWeb(this.sharepointWeb.ID);
            }
        }

        /// <summary>
        /// Create SPContext from site and web urls
        /// </summary>
        /// <param name="contextString">Site url and Web url, joined with a Semicolumn character (;)</param>
        private void CreateContextFromString(string contextString)
        {
            try
            {
                string[] parts = contextString.Split(';');

                string siteUrl = parts[0];
                string webUrl = parts[1];

                SPSite site = new SPSite(siteUrl);
                SPWeb web = site.OpenWeb(webUrl);

                this.sharepointSite = site;
                this.sharepointWeb = web;
            }
            catch (Exception ex)
            {
                // TODO AG: Exception handling here
                throw new ApplicationException("Could not create new SPWeb context", ex);
            }
        }

        /// <summary>
        /// Sets the current SPContext, based on the SPWeb value, if not present. Since the SPContext is based on the HttpContext, 
        /// it is current Http Context that we will actually be setting
        /// </summary>
        private void SetCurrentSpContext()
        {
            // Ensure HttpContext.Current
            if (HttpContext.Current == null)
            {
                HttpRequest request = new HttpRequest(string.Empty, this.sharepointWeb.Url, string.Empty);
                HttpContext.Current = new HttpContext(request, new HttpResponse(TextWriter.Null));
            }

            // SPContext is based on SPControl.GetContextWeb()
            if (HttpContext.Current.Items["HttpHandlerSPWeb"] == null)
            {
                HttpContext.Current.Items["HttpHandlerSPWeb"] = this.sharepointWeb;
            }
        }
        #endregion
    }
}
